﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Jemgine.SpriteBatchExtensions;

namespace Jemgine.Data
{
    public class TerminalInfo
    {
        public String Name;
        public System.Type Type;

        public TerminalInfo(String Name, System.Type Type)
        {
            this.Name = Name == null ? "Name" : Name;
            this.Type = Type;
        }
    }

    public interface Terminal
    {
        void SetTarget(Object obj);
        Object GetTarget();
    }

    public struct ScriptPayload
    {
        public EntityRecord SenderRef;
        public Engine.Physics Physics;

        public static ScriptPayload CreatePayload(EntityRecord E)
        {
            return new ScriptPayload { SenderRef = E, Physics = E.GetComponent<Engine.Physics>() };
        }

        public static ScriptPayload CreatePayload(Engine.Physics P)
        {
            return new ScriptPayload { SenderRef = P.Me, Physics = P };
        }
    }

    public class OutputTerminal : Terminal
    {
        public int TargetID = -1;
     
        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public InputTerminal Target;

        public void Fire(ScriptPayload Payload, int Delay = -1)
        {
            if (Target == null) return;
            Target.Fire(Payload, Delay);
        }

        public OutputTerminal() { }

        public OutputTerminal(OutputTerminal other)
        {
            TargetID = other.TargetID;
            Target = null;
        }

        #region Terminal Members

        public void SetTarget(object obj)
        {
            Target = obj as InputTerminal;
        }

        public object GetTarget()
        {
            return Target;
        }

        #endregion
    }

    public interface ReferenceTarget
    {
        int ReferenceID { get; set; }
        Vector2 GetWorldPosition();
    }

    public class ReferenceTerminal : Terminal
    {
        public int TargetID = -1;

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public ReferenceTarget Target;

        public ReferenceTarget GetTarget(ScriptPayload Payload)
        {
            if (Target == null) return Payload.SenderRef as ReferenceTarget;
            return Target;
        }

        public ReferenceTerminal() { }

        public ReferenceTerminal(ReferenceTerminal other)
        {
            TargetID = other.TargetID;
            Target = null;
        }

        #region Terminal Members

        public void SetTarget(object obj)
        {
            Target = obj as ReferenceTarget;
        }

        public object GetTarget()
        {
            return Target;
        }

        #endregion
    }

    public class InputTerminal
    {
        public ScriptNode Me;
        public int ID;
        public int LocalID;

        public void Fire(ScriptPayload Payload, int Delay)
        {
            if (Me == null) return;
            Engine.ScriptModule Module = Me.Sim.GetModule<Engine.ScriptModule>();
            if (Module != null) Module.FireTerminal(this, Payload, Delay);
        }

        public InputTerminal() { }

        public InputTerminal(ScriptNode Me, InputTerminal other)
        {
            this.Me = Me;
            ID = other.ID;
            LocalID = other.LocalID;
        }
    }

    public interface IHasOutputTerminals
    {
        List<Data.OutputTerminal> GetTerminals();
        Vector2 GetOutputOffset(int Index);
        Vector2 GetWorldPosition();
        System.Type GetOutputType(int Index);
    }

    public class ScriptNode : MapElement, EditorDrawable, Translatable, ReferenceTarget, IHasOutputTerminals
    {
        List<Data.OutputTerminal> IHasOutputTerminals.GetTerminals() { return this.OutputTerminals; }
        Vector2 IHasOutputTerminals.GetOutputOffset(int Index) { return this.GetOutputOffset(Index); }
        Vector2 IHasOutputTerminals.GetWorldPosition() { return this.GetWorldPosition(); }
        System.Type IHasOutputTerminals.GetOutputType(int Index) { return this.OutputTerminalInfo[Index].Type; }

        public String Name { get; set; }
        public bool Enabled { get; set; }

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public Engine.Simulation Sim = null;

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public int ReferenceID { get; set; }

        public ScriptNode()
        {
            Enabled = true;
        }

        public virtual void SetActive(bool Active)
        {
            Enabled = Active;
        }

        public Vector2 Position { get; set; }

        #region MapElementMembers

        public void GatherDependencies(Action<String, String, String> Callback) { }

        public Vector2 GetWorldPosition()
        {
            return _getWorldPosition();
        }

        protected virtual Vector2 _getWorldPosition() { return Position; }

        public Vector2 GetPosition()
        {
            return Position;
        }

        public void SetPosition(Vector2 Pos) { Position = Pos; }

        public Jemgine.Engine.Selection.Types SelectionType()
        {
            return Jemgine.Engine.Selection.Types.MapElement;
            //return Jemgine.Engine.Selection.Types.ScriptNode;
        }

        public virtual void Initialize(Microsoft.Xna.Framework.Content.ContentManager Content) { }         

         public int AssignTargetIDs(int NextID)
         {
             ReferenceID = NextID;
             return NextID + 1;
         }

         public void ClearAllIDs()
         {
             foreach (var Input in InputTerminals)
                 Input.ID = -1;
             ReferenceID = -1;
         }

        public void EnumerateTargets(List<InputTerminal> Inputs, List<ReferenceTarget> Targets) 
        {
            Targets.Add(this);
            foreach (InputTerminal Input in InputTerminals) Inputs.Add(Input); 
        }

        public void PreProcess()
        {
            foreach (OutputTerminal Output in OutputTerminals)
            {
                if (Output.Target == null)
                    Output.TargetID = -1;
                else
                    Output.TargetID = Output.Target.ID;
            }

            foreach (ReferenceTerminal Reference in ReferenceTerminals)
            {
                if (Reference.Target == null)
                    Reference.TargetID = -1;
                else
                    Reference.TargetID = Reference.Target.ReferenceID;
            }
        }


        public void PostProcess(Vector2 Offset, MapData Map, Engine.Simulation Sim)
        {
            this.Sim = Sim;
            this.Position += Offset;
            foreach (var Output in OutputTerminals)
                Output.Target = Map.GetOutputTarget(Output);
            foreach (var Reference in ReferenceTerminals)
                Reference.Target = Map.GetReferenceTarget(Reference);

            if (this.Name == null)
                this.Name = this.GetType().Name;
        }

        public MapElement Clone()
        {
            ScriptNode NewComponent = (ScriptNode)(Activator.CreateInstance(this.GetType()));

            NewComponent.OutputTerminals.Clear();
            foreach (var Output in OutputTerminals)
                NewComponent.OutputTerminals.Add(new OutputTerminal(Output));
            NewComponent.InputTerminals.Clear();
            foreach (var Input in InputTerminals)
                NewComponent.InputTerminals.Add(new InputTerminal(NewComponent, Input));
            NewComponent.ReferenceTerminals.Clear();
            foreach (var Reference in ReferenceTerminals)
                NewComponent.ReferenceTerminals.Add(new ReferenceTerminal(Reference));

            NewComponent.OutputTerminalInfo = new List<TerminalInfo>(OutputTerminalInfo);
            NewComponent.InputTerminalInfo = new List<TerminalInfo>(InputTerminalInfo);
            NewComponent.ReferenceTerminalInfo = new List<TerminalInfo>(ReferenceTerminalInfo);

            foreach (System.Reflection.PropertyInfo Property in this.GetType().GetProperties())
                if (Property.CanRead && Property.CanWrite)
                    Property.SetValue(NewComponent, Property.GetValue(this, null), null);

            return NewComponent;
        }

        #endregion

        public void CopyProperties(ScriptNode From)
        {
            System.Diagnostics.Debug.Assert(this.GetType() == From.GetType());

            foreach (System.Reflection.PropertyInfo Property in this.GetType().GetProperties())
                if (Property.CanRead && Property.CanWrite)
                    Property.SetValue(this, Property.GetValue(From, null), null);
        }

        public List<OutputTerminal> OutputTerminals = new List<OutputTerminal>();

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public List<InputTerminal> InputTerminals = new List<InputTerminal>();
        
        public List<ReferenceTerminal> ReferenceTerminals = new List<ReferenceTerminal>();

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public List<TerminalInfo> OutputTerminalInfo = new List<TerminalInfo>();
        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public List<TerminalInfo> InputTerminalInfo = new List<TerminalInfo>();
        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public List<TerminalInfo> ReferenceTerminalInfo = new List<TerminalInfo>();

        public void AddOutput(String Name, System.Type Type)
        {
            OutputTerminalInfo.Add(new TerminalInfo(Name, Type));
            OutputTerminals.Add(new OutputTerminal());
        }

        public void AddInput(String Name, System.Type Type)
        {
            InputTerminalInfo.Add(new TerminalInfo(Name, Type));
            InputTerminals.Add(new InputTerminal { Me = this, LocalID = InputTerminals.Count });
        }

        public void AddReference(String Name, System.Type Type)
        {
            ReferenceTerminalInfo.Add(new TerminalInfo(Name, Type));
            ReferenceTerminals.Add(new ReferenceTerminal());
        }

        public void ClearOutputs()
        {
            OutputTerminalInfo.Clear();
            OutputTerminals.Clear();
        }

        public void ClearInputs()
        {
            InputTerminalInfo.Clear();
            foreach (var Terminal in InputTerminals) Terminal.ID = -1;
            InputTerminals.Clear();
        }

        public void ClearReferences()
        {
            ReferenceTerminals.Clear();
            ReferenceTerminalInfo.Clear();
        }

        public virtual void FireTerminal(int InputID, ScriptPayload Payload) { }

        #region EditorDrawable Members

        public Vector2 GetInputOffset(int Index)
        {
            Vector2 Dims = GetDimensions();
            Vector2 R = new Vector2(-Dims.X / 2, Dims.Y / 2);
            R.Y -= ( Index + 1 ) * ( Dims.Y / ( InputTerminals.Count + 1 ) );
            return R;            
            //Vector2 result = new Vector2(-1, 0);
            //Matrix M = Matrix.CreateRotationZ(MathHelper.ToRadians(-35 * Index));
            //return Vector2.Transform(result, M);
        }

        public Vector2 GetOutputOffset(int Index)
        {
            Vector2 Dims = GetDimensions();
            Vector2 R = new Vector2(Dims.X / 2, Dims.Y / 2);
            R.Y -= (Index + 1) * ( Dims.Y / (OutputTerminals.Count + 1) ); 
            return R;
            //if (OutputTerminals[Index].Target == null)
            //{
            //    Vector2 result = new Vector2(1, 0);
            //    Matrix M = Matrix.CreateRotationZ(MathHelper.ToRadians(-35 * Index));
            //    return Vector2.Transform(result, M);
            //}
            //else
            //{
                //Vector2 Delta = OutputTerminals[Index].Target.Me.GetWorldPosition() - this.GetWorldPosition();
                //Delta.Normalize();
                //return Delta;
            //}
        }

        public virtual Vector2 GetReferenceOffset(int Index)
        {
            Vector2 Dims = GetDimensions();
            Vector2 R = new Vector2(Dims.X / 2, Dims.Y / 2);
            R.X -= ( Index + 1 ) * ( Dims.X / ( ReferenceTerminals.Count + 1 ) );
            return R;
            //if (ReferenceTerminals[Index].Target == null)
            //{
            //    Vector2 result = new Vector2(0, 1);
            //    Matrix M = Matrix.CreateRotationZ(MathHelper.ToRadians(-35 * Index));
            //    return Vector2.Transform(result, M);
            //}
            //else
            //{
            //    Vector2 Delta = ReferenceTerminals[Index].Target.GetWorldPosition() - this.GetWorldPosition();
            //    Delta.Normalize();
            //    return Delta;
            //}
        }

        public Vector2 GetDimensions()
        {
            return new Vector2(System.Math.Max(ReferenceTerminals.Count, 5), System.Math.Max(System.Math.Max(
                InputTerminals.Count + 1, OutputTerminals.Count + 1), 3));
        }

        public void DrawConnection(Jemgine.Renderer.RenderContext Context, Vector2 P0, Vector2 P1, Vector2 P2, Vector2 P3)
        {
            List<Vector2> SegmentPoints = new List<Vector2>();
            int segments = 20;
            float Step = 1.0f / (float)segments;
            for (int i = 0; i <= segments; ++i)
                SegmentPoints.Add(Engine.Bezier.Point(P0, P1, P2, P3, Step * i));

            for (int i = 1; i < SegmentPoints.Count; ++i)
                Context.DrawLine(SegmentPoints[i - 1], SegmentPoints[i], 0.2f);
        }

        public void Draw(int ID, EditorDrawData Context, Data.SelectionType Selected)
        {
           Context.Renderer.PushMatrix(Matrix.CreateTranslation(Position.X, Position.Y, 0.0f));
            if (Selected == Data.SelectionType.NotSelected)
            {
                String Label = "";
                if (Name != null && Name.Length != 0) Label = Name;
                else Label = this.GetType().Name;
                Context.AddLabel(ID.ToString() + "\n" + Label, GetWorldPosition(), LabelType.Name);
            }

              
            Context.Renderer.Color = EditorDrawData.DrawColor(Selected);
            //Context.Renderer.DrawSprite(Icon, Vector2.Zero, new Vector2(2, 2), 0, false);

            Vector4 SemiColor = Context.Renderer.Color;
            SemiColor.W = 0.25f;
            Context.Renderer.Color = SemiColor;
            
            Context.Renderer.DrawSprite(Context.Renderer.SolidTexture, Vector2.Zero, GetDimensions(), 0.0f, false, false);

            SemiColor.W = 1.0f;
            Context.Renderer.Color = SemiColor;
            Context.Renderer.DrawBox(-GetDimensions() / 2, GetDimensions() / 2, 0.1f);

            Context.Renderer.PopMatrix();

            if (Selected == Data.SelectionType.NotSelected)
            {
                Context.Renderer.PushCleanMatrix(Matrix.Identity);
                Context.Renderer.Color = Color.Green.ToVector4();

                for (int I = 0; I < OutputTerminals.Count; ++I)
                {
                    Context.Renderer.DrawPoint(GetWorldPosition() + GetOutputOffset(I));

                    if (OutputTerminals[I].Target != null)
                    {
                        Vector2 StartPoint = GetOutputOffset(I) + this.GetWorldPosition();
                        Vector2 EndPoint = OutputTerminals[I].Target.Me.GetWorldPosition()
                            + OutputTerminals[I].Target.Me.GetInputOffset(OutputTerminals[I].Target.LocalID);

                        DrawConnection(Context.Renderer, StartPoint, StartPoint + new Vector2(4, 0),
                            EndPoint + new Vector2(-4, 0), EndPoint);
                    }
                }

                Context.Renderer.Color = Color.Yellow.ToVector4();

                for (int I = 0; I < ReferenceTerminals.Count; ++I)
                {
                    Context.Renderer.DrawPoint(GetWorldPosition() + GetReferenceOffset(I));

                    if (ReferenceTerminals[I].Target != null)
                    {
                        Vector2 StartPoint = GetReferenceOffset(I) + this.GetWorldPosition();
                        Vector2 EndPoint = ReferenceTerminals[I].Target.GetWorldPosition();

                        DrawConnection(Context.Renderer, StartPoint, StartPoint + new Vector2(0, 4),
                            EndPoint, EndPoint);
                    }
                }

                Context.Renderer.Color = Color.Red.ToVector4();

                for (int I = 0; I < InputTerminals.Count; ++I)
                    Context.Renderer.DrawPoint(GetWorldPosition() + GetInputOffset(I));

                Context.Renderer.PopMatrix();
            }

            if (Selected == Data.SelectionType.Selected)
            {
                for (int I = 0; I < OutputTerminals.Count; ++I)
                    Context.AddLabel(OutputTerminalInfo[I].Name,
                        GetWorldPosition() + GetOutputOffset(I),
                        LabelType.Output);

                for (int I = 0; I < ReferenceTerminals.Count; ++I)
                    Context.AddLabel(ReferenceTerminalInfo[I].Name,
                        GetWorldPosition() + GetReferenceOffset(I),
                        LabelType.Reference);

                for (int I = 0; I < InputTerminals.Count; ++I)
                    Context.AddLabel(InputTerminalInfo[I].Name,
                        GetWorldPosition() + GetInputOffset(I),
                        LabelType.Input);
            }
  
        }

        #endregion

        #region Translatable Members

        object Translatable.PrepareToTranslate()
        {
            return Position;
        }

        void Translatable.ApplyTranslation(object Tag, Vector2 MouseDelta)
        {
            Vector2 OriginalPosition = (Vector2)Tag;
            Position = OriginalPosition + MouseDelta;
        }

        void Translatable.Snap(Snapper Snapper)
        {
            Position = Snapper.SnapPosition(Position);
        }

        #endregion
    }
}
